#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEFAULT_N 1000000000

int main(int argc, char **argv) {
    double local, pi_seq = 0.0, pi_llc = 0.0, pi_mpi = 0.0, gpi_mpi, w;
    struct timeval start_time, end_time;
    double exe_time_seq, exe_time_llc, exe_time_mpi;
    long i, N;
    int MPI_NAME, MPI_NUMPROCESSORS;

    N = (argc > 1) ? atol(argv[1]) : DEFAULT_N;
    w = 1.0 / N;

    /* SEQUENTIAL EXECUTION */

    gettimeofday(&start_time, 0);
    
    {
        pi_seq = 0.0;
        for (i = 0; i < N; i++) {
            local = (i + 0.5)*w;
            pi_seq = pi_seq + 4.0/(1.0 + local*local);
        }
    }

    gettimeofday(&end_time, 0);
    exe_time_seq = end_time.tv_sec - start_time.tv_sec;
    exe_time_seq = exe_time_seq + (end_time.tv_usec - start_time.tv_usec) / 1e6;
    pi_seq *= w;

    /* END SEQUENTIAL EXECUTION */

    /* LLC EXECUTION */

    w = 1.0 / N;

    gettimeofday(&start_time, 0);

    #pragma omp target device(mpi)
    #pragma omp parallel private(i, local) 
    {
        #pragma omp single
        pi_llc = 0.0;

        #pragma omp for reduction (+: pi_llc)
        for (i = 0; i < N; i++) {
            local = (i + 0.5) * w;
            pi_llc = pi_llc + 4.0 / (1.0 + local * local);
        }
    }

    gettimeofday(&end_time, 0);
    exe_time_llc = end_time.tv_sec - start_time.tv_sec;
    exe_time_llc = exe_time_llc + (end_time.tv_usec - start_time.tv_usec) / 1e6;
    pi_llc *= w;

    /* END LLC EXECUTION */

    /* MPI EXECUTION */

    MPI_Comm_size(MPI_COMM_WORLD, &MPI_NUMPROCESSORS);
    MPI_Comm_rank(MPI_COMM_WORLD, &MPI_NAME);

    w = 1.0 / N;

    gettimeofday(&start_time, 0);

    for (i = MPI_NAME; i < N; i += MPI_NUMPROCESSORS) {
        local = (i + 0.5) * w;
        pi_mpi = pi_mpi + 4.0 / (1.0 + local * local);
    }
    MPI_Allreduce(&pi_mpi, &gpi_mpi, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
    MPI_Barrier(MPI_COMM_WORLD);

    gettimeofday(&end_time, 0);
    exe_time_mpi = end_time.tv_sec - start_time.tv_sec;
    exe_time_mpi = exe_time_mpi + (end_time.tv_usec - start_time.tv_usec) / 1e6;
    pi_mpi = gpi_mpi * w;

    /* END MPI EXECUTION */

    printf("PI - SEQUENTIAL EXECUTION: %f\tTime: %f\n", pi_seq, exe_time_seq);
    printf("PI - LLC EXECUTION: %f\tTime: %f\n", pi_llc, exe_time_llc);
    printf("PI - MPI EXECUTION: %f\tTime: %f\n", pi_mpi, exe_time_mpi);

    return 0;
}

